package com.xu.cloud.common.advice.exception;

import com.xu.cloud.common.constant.OrderConstant;
import com.xu.cloud.common.exception.ErrorCodeEnum;
import com.xu.cloud.common.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.stream.Collectors;


/**
 * 全局异常处理类
 *
 * @author persimmon
 * @since 2023/4/22
 */
@Order(OrderConstant.HTTP_EXCEPTION_HANDLER_ORDER)
@Slf4j
@RestControllerAdvice
public class HttpExceptionHandlerAdvice {

    /**
     * 请求地址不存在, 找不到对应的{@link org.springframework.web.servlet.HandlerMapping}会抛出{@link NoHandlerFoundException}异常
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(code = HttpStatus.NOT_FOUND)
    public Result<?> handlerNoHandlerFoundException(NoHandlerFoundException noHandlerFoundException) {
        log.error("捕获到NoHandlerFoundException请求地址不存在异常, message: ", noHandlerFoundException);
        return Result.fail(ErrorCodeEnum.NOT_FOUND.getCode(), ErrorCodeEnum.NOT_FOUND.getMessage(), null);
    }

    /**
     * 表单提交的请求, 参数校验失败会抛出{@link BindException}异常
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public Result<?> handlerBindException(BindException bindException) {
        log.error("捕获到BindException, JSR-303参数校验异常, message: ", bindException);
        return Result.fail(ErrorCodeEnum.BAD_REQUEST.getCode(), bindingResultCollectErrMsg(bindException.getBindingResult()), null);
    }

    /**
     * 使用{@link org.springframework.web.bind.annotation.RequestBody}注解标注的Json请求体,
     * 参数校验失败会抛出{@link MethodArgumentNotValidException}异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public Result<?> handlerMethodArgumentNotValidException(MethodArgumentNotValidException methodArgumentNotValidException) {
        log.error("捕获到MethodArgumentNotValidException, JSR-303参数校验异常, message: ", methodArgumentNotValidException);
        return Result.fail(ErrorCodeEnum.BAD_REQUEST.getCode(), bindingResultCollectErrMsg(methodArgumentNotValidException.getBindingResult()), null);
    }


    /**
     * 在Controller类上使用{@link org.springframework.validation.annotation.Validated}注解标注,
     * 在字段参数上使用{@link javax.validation.Valid}注解标注,
     * 或者加上一个不做处理的{@link BindingResult} bindingResult参数,
     * 参数校验失败会抛出{@link ConstraintViolationException}异常
     * <p>
     * eg:
     * <blockquote>
     * <pre>{@code
     * @Validated
     * @RestController
     * @RequestMapping("/valid")
     * public class ConstraintViolationController {
     *     @PostMapping("/fieldConstraintViolationException")
     *     public void fieldConstraintViolationException(@Valid
     *                                              @NotBlank(message = "姓名不能为空") String name,
     *                                              @NotBlank(message = "手机号不能为空") String mobileNo) {
     *         System.out.println(name + ": " + mobileNo);
     *     }
     *
     *     @PostMapping("/constraintViolationException")
     *     public void constraintViolationException(@Valid @RequestBody PersonWithDog PersonWithDog,
     *                                              BindingResult bindingResult) {
     *         System.out.println(PersonWithDog.getDog().getName());
     *     }
     * }
     *
     * @Data
     * class PersonWithDog {
     *     @NotBlank(message = "姓名不能为空")
     *     private String name;
     *     @Valid
     *     private Dog dog;
     * }
     *
     * @Data
     * class Dog {
     *     @NotBlank(message = "狗的姓名不能为空")
     *     private String name;
     * }
     * }</pre>
     * </blockquote>
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public Result<?> handlerConstraintViolationException(ConstraintViolationException constraintViolationException) {
        log.error("捕获到ConstraintViolationException, JSR-303参数校验异常, message: ", constraintViolationException);
        return Result.fail(ErrorCodeEnum.BAD_REQUEST.getCode(), constraintViolationException.getMessage(), null);
    }

    /**
     * 将BindingResult收集成异常信息
     */
    private String bindingResultCollectErrMsg(BindingResult bindingResult) {
        // 校验的错误信息
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        // 收集错误
        String errorMsg = fieldErrors.stream()
                .map(error -> {
                            String field = error.getField();
                            String defaultMessage = error.getDefaultMessage();
                            return String.format("字段名称:[%s], 描述信息:[%s]", field, defaultMessage);
                        }
                )
                .collect(Collectors.joining("; "));
        return errorMsg;
    }
}
